package com.jvm;

/**
 * 双重检查与 volatile
 *
 * @author: 李德才
 * @description:
 * @create: 2021-06-19 16:03
 **/
public class JVM_13_DoubleCheck_Volatile {

    public static void main(String[] args) {
        Integer instance = new JVM_13_DoubleCheck_Volatile().getInstance();
    }

    /**
     * 为什么需要双重检查？
     * 如果有两个线程同时到达 synchronized ，
     * 在 synchronized 中没有判断实例是否为空，
     * 则会出现创建两个对象的情况，
     **/

    /**
     * 为什么要加 volatile
     * 因为对象的创建分为分配内存空间、将对象指向内存地址、为对象赋初始值、为对象赋默认值
     * 另外在代码执行过程中会出现指令重排的现象
     * 所以正常的创建对象的逻辑是：分配内存空间、将对象指向内存地址、为对象赋初始值、为对象赋默认值、将创建的对象赋值给INSTANCE
     * 但是如果出现指令重排就会出现 先将对象赋值给INSTANCE，然后再赋予对象默认值
     * 但是如果当对象没有被赋予默认值的时候被使用，则使用的就是初始值，也就是此时 INSTANCE = 0 ！，而不是我们期望的 200
     **/

    /**
     *
     * 创建对象的过程
     * ------------------------
     * new <java/lang/Object>
     * dup
     * invokespecial #1 <java/lang/Object.<init>>
     * astore_1
     * return
     **/

    /**
     * volatile 实现细节
     * 因为涉及到很多层面，这里展开来说
     * 1.在字节码层面（class 文件） 只是增加了 volatile 标识
     * 2.在JVM层面 volatile 内存区的读写都加屏障
     * 3.在OS和硬件层面，不同的环境有不同的实现，windows 是使用 lock 指令实现
     **/

    private volatile Integer INSTANCE = null;

    public Integer getInstance() {
        if (INSTANCE == null) {
            /**
             * synchronized 实现细节
             * 分层面来说
             * 1.字节码层面
             * ACC_synchronized
             * monitorEnter monitorExit
             * 2.JVM层面
             * C/C++ 调用了操作系统提供的同步机制
             * 3.OS和硬件层面
             * X86: lock：先锁住，然后比对 ---> lock => CAS（compareAndSet） 操作
             *
             **/
            synchronized (JVM_13_DoubleCheck_Volatile.class) {
                if (INSTANCE == null) {
                    INSTANCE = 200;
                }
            }
        }
        return INSTANCE;
    }
}
